package com.nc.interview.javase;

import org.junit.Test;

import java.math.BigDecimal;
import java.util.*;

/**
 * @author ymsxyz
 * @version 1.0
 * @description 面试宝典--javaSE部分
 * @date 2021/2/9 11:20
 */
public class JavaSETest {

    /**
     * 问题:2进制的小数无法精确的表达10进制小数，计算机在计算10进制小数的过程中要先转换为2进制进行计算，这个过程中出现了误差。
     * 解决:使用java.math.BigDecimal。
     *
     * @param:
     * @return: void
     * @author: ymsxyz
     * @date: 2021/2/9
     */
    @Test
    public void decimalism2BinaryCalculateTest() {
        System.out.println(4.0 - 3.6);//0.3999999999999999

        BigDecimal bg1 = new BigDecimal("4.0");
        BigDecimal bg2 = new BigDecimal("3.6");
        BigDecimal bg3 = bg1.subtract(bg2);
        System.out.println(bg3);//0.4
    }

    /**
     * 为什么重写equals还要重写hashcode？
     * 当把自定义对象添加到去重集合时，如hashset，hashset会先调用hashcode()方法，判断新对象与已有对象的hashcode值是否相等，相等则继续调用equals判断，相等则是同一个对象，否则相反。
     *
     * @param:
     * @return: void
     * @author: ymsxyz
     * @date: 2021/2/9
     */
    @Test
    public void hashcodeAndEqualsTest() {
/*
        HashMap<String, String> hashMap = new HashMap<>();
        HashMap<String, String> hashMap1 = new HashMap<>();

        hashMap.put("1", "小明");
        hashMap1.put("1", "小明");

        System.out.println(hashMap.equals(hashMap1));//true
        System.out.println(hashMap == hashMap1);//false
*/

        //自定义对象比较
        Student student = new Student();
        Student student1 = new Student();
        Student student2 = new Student();
        String s = "小红";
        String s1 = "小红";
        String s2 = new String("小红");
        String s3 = "小红" + "";
        StringBuffer s4 = new StringBuffer("小红");
        String s5 = s4.toString();

        student.setName(s);
        student1.setName(s1);
        student2.setName(s5);
        System.out.println(student.hashCode());//762995 不重写:381707837
        System.out.println(student1.hashCode());//762995 不重写:589446616
        System.out.println(student2.hashCode());//762995 不重写:589446616
        System.out.println(student.equals(student1));//true
        System.out.println(student.equals(student2));//true Strng的equal方法jdk已经重写比较内容

        //hashSet
        HashSet<Student> students = new HashSet<>();
        students.add(student);
        students.add(student1);
        students.add(student2);
        System.out.println(students.size());//1
    }

    /**
     * map分类
     * Map主要用于存储健值对，根据键得到值，因此不允许键重复(重复了覆盖了),但允许值重复。
     * <p>
     * Hashmap 是一个最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值，具有很快的访问速度，遍历时，取得数据的顺序是完全随机的。 HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null;HashMap不支持线程的同步，即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步，可以用 Collections的synchronizedMap方法使HashMap具有同步的能力，或者使用ConcurrentHashMap。
     * <p>
     * Hashtable与 HashMap类似,它继承自Dictionary类，不同的是:它不允许记录的键或者值为空;它支持线程的同步，即任一时刻只有一个线程能写Hashtable,因此也导致了 Hashtable在写入时会比较慢。
     * <p>
     * LinkedHashMap 是HashMap的一个子类，保存了记录的插入顺序，在用Iterator遍历LinkedHashMap时，先得到的记录肯定是先插入的.也可以在构造时用带参数，按照应用次数排序。在遍历的时候会比HashMap慢，不过有种情况例外，当HashMap容量很大，实际数据较少时，遍历起来可能会比 LinkedHashMap慢，因为LinkedHashMap的遍历速度只和实际数据有关，和容量无关，而HashMap的遍历速度和他的容量有关。
     * <p>
     * TreeMap实现SortedMap接口，能够把它保存的记录根据键排序,默认是按键值的升序排序，也可以指定排序的比较器，当用Iterator 遍历TreeMap时，得到的记录是排过序的。
     * <p>
     * 一般情况下，我们用的最多的是HashMap,在Map 中插入、删除和定位元素，HashMap 是最好的选择。但如果您要按自然顺序或自定义顺序遍历键，那么TreeMap会更好。如果需要输出的顺序和输入的相同,那么用LinkedHashMap 可以实现,它还可以按读取顺序来排列.
     *
     * @param:
     * @return: void
     * @author: ymsxyz
     * @date: 2021/2/9
     */
    @Test
    public void mapTest() {
        HashMap<String, String> hashMap = new HashMap<>();
        LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
        TreeMap treeMap = new TreeMap<>();
        Hashtable<String, String> hashtable = new Hashtable<>();

        linkedHashMap.put("0", "xiao");
        linkedHashMap.put("3", null);
        linkedHashMap.put("2", "xiao");
        linkedHashMap.put("4", "xiao");
        linkedHashMap.put(null, null);

        hashMap.put("0", "xiao");
        hashMap.put("3", null);
        hashMap.put("2", "xiao");
        hashMap.put("4", "xiao");
        hashMap.put(null, null);

        treeMap.put("0", "xiao");
        treeMap.put("3", null);
        treeMap.put("5", null);
        treeMap.put("2", "xiao");
        treeMap.put("4", "xiao");
        //treeMap.put(null, null);空指针异常

        hashtable.put("0", "xiao");
        hashtable.put("3", "xiao");
        //hashtable.put("3", null);空指针异常
        hashtable.put("2", "xiao");
        hashtable.put("4", "xiao");
        //hashtable.put(null, "xiao");空指针异常
    }

    /**
     * 当用final修饰一个类时，表明这个类不能被继承。也就是说，如果一个类你永远不会让他被继承，就可以用final进行修饰。final类中的成员变量可以根据需要设为final，但是要注意final类中的所有成员方法都会被隐式地指定为final方法。
     * <p>
     * “使用final方法的原因有两个。第一个原因是把方法锁定，以防任何继承类修改它的含义；第二个原因是效率。在早期的Java实现版本中，会将final方法转为内嵌调用。但是如果方法过于庞大，可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中，不需要使用final方法进行这些优化了。“
     * <p>
     * 对于一个final变量，如果是基本数据类型的变量，则其数值一旦在初始化之后便不能更改；如果是引用类型的变量，则在对其初始化之后便不能再让其指向另一个对象。
     *
     * @param:
     * @return: void
     * @author: ymsxyz
     * @date: 2021/2/9
     */
    @Test
    public void finalTest() {

    }


}

/**
 * @author ymsxyz
 * @version 1.0
 * @description 成员变量为引用类型
 * @date 2021/2/9 12:34
 */
//class Student extends People{ final类不能被继承,编译错误
class Student{
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {//同一地址
            return true;
        }
        if (o == null || getClass() != o.getClass()) {//若参数为null或不是同一class文件
            return false;
        }

        Student student = (Student) o;
        //若name不为null则递归比较name成员对象(第一步确定不是同一地址),若为null则直接和参数name属性比较
        return name != null ? name.equals(student.name) : student.name == null;
    }

    //set中会先判断hashCode,再判断equal
    @Override
    public int hashCode() {
        return name != null ? name.hashCode() : 0;
    }
}

/**
 * @author ymsxyz
 * @version 1.0
 * @description 成员变量为基本类型
 * @date 2021/2/9 12:31
 */
class Teacher {
    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        Teacher teacher = (Teacher) o;

        return age == teacher.age;//基本类型数据直接比较,不用递归
    }

    @Override
    public int hashCode() {
        return age;
    }
}

/**
 * @author ymsxyz
 * @version 1.0
 * @description abstract和final不能同时修饰类或方法,意义冲突:abstract类或方法必须重写实现,而final修饰不能重写,没意义了
 * @date 2021/2/9 12:31
 */
final class People {
    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    //private abstract void abstractMethod();

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        People people = (People) o;

        return age == people.age;
    }

    @Override
    public int hashCode() {
        return age;
    }
}

